﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;

namespace Gemli.Data.Linq
{
    internal class QueryTranslator<TModel> : ExpressionVisitor
        where TModel : DataModel
    {
        DataModelQuery<TModel> DataModelQuery;
        DataModelQueryCondition<TModel> CurrentCondition;

        internal QueryTranslator()
        {
            this.DataModelQuery = new DataModelQuery<TModel>();
            this.CurrentCondition = new DataModelQueryCondition<TModel>(MemberMappingKeyType.ClrMember, this.DataModelQuery);
        }

        internal QueryTranslator(DataModelQuery<TModel> dataModelQuery)
        {
            this.DataModelQuery = dataModelQuery;
            this.CurrentCondition = new DataModelQueryCondition<TModel>(MemberMappingKeyType.ClrMember, this.DataModelQuery);
        }

        internal DataModelQuery<TModel> Translate(Expression expression)
        {
            this.Visit(expression);
            DataModelQuery.Conditions.Add(CurrentCondition);
            return DataModelQuery;
        }

        private static Expression StripQuotes(Expression e)
        {
            while (e.NodeType == ExpressionType.Quote)
            {
                e = ((UnaryExpression)e).Operand;
            }
            return e;
        }

        private static string LastMethodEval;
        protected override Expression VisitMethodCall(MethodCallExpression m)
        {
            if (m.Method.DeclaringType == typeof(Queryable))
            {
                switch (m.Method.Name) {
                    case "Where":
                        LambdaExpression lambda = (LambdaExpression)StripQuotes(m.Arguments[1]);
                        this.Visit(lambda.Body);
                        LastMethodEval = m.Method.Name;
                        return m;
                    case "OrderBy":
                    case "ThenBy":
                        if (m.Method.Name == "ThenBy" && LastMethodEval != "OrderBy" && LastMethodEval != "ThenBy") break;
                        Visit(m.Arguments[0]);
                        LastMethodEval = m.Method.Name;
                        LambdaExpression lambda2 = (LambdaExpression)StripQuotes(m.Arguments[1]);
                        var body = lambda2.Body;
                        if (body.NodeType == ExpressionType.MemberAccess)
                        {
                            var membody = (MemberExpression)body;
                            this.DataModelQuery.AddSortItem(membody.Member.Name, false);
                            return m;
                        }
                        else if (body.NodeType == ExpressionType.Constant)
                        {
                            var valbody = (ConstantExpression)body;
                            if (valbody.Value is string)
                            {
                                this.DataModelQuery.AddSortItem(valbody.Value.ToString());
                            }
                            return m;
                        }
                        break;
                }
            }
            throw new NotSupportedException(string.Format("The method '{0}' is not supported", m.Method.Name));
        }

        protected override Expression VisitUnary(UnaryExpression u)
        {
            if (u.NodeType == ExpressionType.Convert)
            {
                var t = u.Type;
                var ex = u.Operand;
                if (ex.NodeType == ExpressionType.Call)
                {
                    var mex = u.Operand as MethodCallExpression;
                    switch (mex.Method.Name)
                    {
                        case "GetPropertyValueByColumnName":
                            this.CurrentCondition.EvalSubject = (string)((ConstantExpression)mex.Arguments[0]).Value;
                            this.CurrentCondition.FindMemberMappingBy = MemberMappingKeyType.DbColumn;
                            return u;
                    }
                }
                else if (ex.NodeType == ExpressionType.MemberAccess)
                {
                    var pex = u.Operand as MemberExpression;
                    this.CurrentCondition.EvalSubject = pex.Member.Name;
                    this.CurrentCondition.FindMemberMappingBy = MemberMappingKeyType.ClrMember;
                    return u;
                }
            }
            throw new NotSupportedException(string.Format("The unary operator '{0}' is not supported", u.NodeType));
        }

        protected override Expression VisitBinary(BinaryExpression b)
        {
            this.Visit(b.Left);
            switch (b.NodeType)
            {
                case ExpressionType.Or:
                    throw new NotImplementedException("OR expression not implemented.");
                case ExpressionType.And:
                case ExpressionType.AndAlso:
                    this.DataModelQuery.Conditions.Add(this.CurrentCondition);
                    this.CurrentCondition = new DataModelQueryCondition<TModel>(MemberMappingKeyType.ClrMember, this.DataModelQuery);
                    break; // handled
                case ExpressionType.Equal:
                    CurrentCondition.CompareOp = Compare.Equal;
                    break;
                case ExpressionType.NotEqual:
                    CurrentCondition.CompareOp = Compare.NotEqual;
                    break;
                case ExpressionType.LessThan:
                    CurrentCondition.CompareOp = Compare.LessThan;
                    break;
                case ExpressionType.LessThanOrEqual:
                    CurrentCondition.CompareOp = Compare.LessThanOrEqual;
                    break;
                case ExpressionType.GreaterThan:
                    CurrentCondition.CompareOp = Compare.GreaterThan;
                    break;
                case ExpressionType.GreaterThanOrEqual:
                    CurrentCondition.CompareOp = Compare.GreaterThanOrEqual;
                    break;
                default:
                    throw new NotSupportedException(string.Format("The binary operator '{0}' is not supported", b.NodeType));
            }
            this.Visit(b.Right);
            return b;
        }

        protected override Expression VisitConstant(ConstantExpression c)
        {
            IQueryable q = c.Value as IQueryable;
            if (q != null)
            {
                // assume constant nodes w/ IQueryables are table references
                // .. this is established with TModel
            }
            else if (c.Value == null)
            {
                this.CurrentCondition.CompareOp = 
                    this.CurrentCondition.CompareOp == Compare.Equal
                    ? Compare.Null
                    : this.CurrentCondition.CompareOp == Compare.NotEqual
                        ? Compare.NotNull
                        : this.CurrentCondition.CompareOp;
            }
            else
            {
                this.CurrentCondition.CompareValue = c.Value;
            }
            return c;
        }

        protected override Expression VisitMemberAccess(MemberExpression m)
        {
            if (m.Expression != null && m.Expression.NodeType == ExpressionType.MemberAccess)
            {
                CurrentCondition.EvalSubject = m.Member.Name;
                return m;
            }
            throw new NotSupportedException(string.Format("The member '{0}' is not supported", m.Member.Name));
        }
    }
}
